/**
XenSim simulates xenbal.
Copyright (C) 2006 Travis Vachon

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**/
package xensim;

public class TransferDaemon {
	SimGlobals globals;
	Domain domain;
	Node source;
	Node destination;
	long totalMemoryPages;
	long memoryToTransfer; //in bytes
	long migrationBandwidthLimit;
	double startTime;
	double iterationStartDomainTime;
	double iterationStartTime;
	double dirtyPages;
	
	boolean sourceComplete;
	
	String printHeader;
	
	
	
	
	
	public TransferDaemon(SimGlobals globals,
						  Domain domain,
						  Node source,
						  Node destination){
		this.globals = globals;
		this.domain = domain;
		this.source = source;
		this.destination = destination;
		totalMemoryPages = domain.memoryPages;
		memoryToTransfer = totalMemoryPages * source.pageSize;
		migrationBandwidthLimit = globals.minimumMigrationBandwidth;
		dirtyPages = 0;
		sourceComplete = false;
		
		printHeader = domain.name + ": " + source.name + "->" + destination.name + ": ";
		
		startTime = globals.currentTime;
		
		this.domain.migrating = true;
		this.source.migrating = true;
		this.destination.migrating = true;
		reserveMemory();
		migrationIteration();
	}
	
	protected void migrationQuantum(){
		
		MigrationQuantumEvent migrationQuantumEvent = new MigrationQuantumEvent(globals.currentTime + 1,this);
		/*
		 * Bandwidth negotiation
		 * 
		 * Assuming switch can handle full simultaneous bandwidth. 
		 * ie, if node has 10Mbps connection, it can transmit 10Mb and receive 10Mb in the same second.
		 */
		
		long usableBandwidth = Math.max(source.connection.getMaxAvailRxBandwidth(),
										source.connection.getMaxAvailTxBandwidth());
		
		usableBandwidth = Math.min(usableBandwidth,migrationBandwidthLimit);
		//Make requests and make sure they're the same
		
		long txRequest = source.connection.requestTxBandwidth(migrationQuantumEvent, usableBandwidth);
		long rxRequest = destination.connection.requestRxBandwidth(migrationQuantumEvent, usableBandwidth);
		
		assert txRequest == rxRequest;
		
		
//		long usedSrcBandwidth = source.connection.getUsedRxBandwidth();
//		long usedDestBandwidth = destination.connection.getUsedTxBandwidth();
//		
//			if(source.currentDomain != null){
//				for (Domain domain: source.domains){
//					usedSrcBandwidth += domain.networkUsageFn.getOutRate(globals.currentTime);
//				}
//			}
//				
//			if(destination.currentDomain != null){
//				for(Domain domain: destination.domains){
//					usedDestBandwidth += domain.networkUsageFn.getInRate(globals.currentTime);
//				}
//			}
//
//		
//		/*
//		 * Set the usable bandwidth to the minimum of the available bandwidth of node or the migration bandwidth limit
//		 */
//		
//		long usableBandwidth = Math.min(source.connection.getBandwidth()-usedSrcBandwidth,
//										destination.connection.getBandwidth()-usedDestBandwidth);
//		
//		usableBandwidth = Math.min(usableBandwidth,migrationBandwidthLimit);
//		
//		source.request usableBandwidth;
//		source.usedTransmitBandwidth += usableBandwidth;
		 
		//System.out.println("Bandwidth: " + usableBandwidth);
		
		if (domain.isActive()) {
			// Figure out how many pages will be dirtied in this quantum
			double pageDirtyRate = domain.pagerateFn
					.getRate(globals.currentTime);
			double pagesDirtied = pageDirtyRate * domain.memoryPages;
			// Total pages dirtied this round times the ratio of undirtied pages
			// to total pages
			double newPagesDirtied = ((domain.memoryPages - dirtyPages) / domain.memoryPages)
					* pagesDirtied;
			dirtyPages += newPagesDirtied;
		}
		//System.out.println(printHeader + "Migration quantum");
		
		globals.eventQueue.add(migrationQuantumEvent);
		
		
	}
	
	protected void migrationIteration(){
		System.out.println(printHeader + "Iteration: " + memoryToTransfer);
		iterationStartDomainTime = domain.getDomainTime();
		iterationStartTime = globals.currentTime;
		
		
		
		if(migrationBandwidthLimit > globals.maximumMigrationBandwidth
				|| memoryToTransfer < 256*globals.KB){
					
			finalizeSource();
				
		}
				
		migrationQuantum();
		
	}
	
	
	protected void migrationIterationFinish(){
		
		System.out.println(printHeader + "Iteration Finished: " + (globals.currentTime-iterationStartTime) +" milliseconds");
		
		if(dirtyPages != 0 ){
			System.out.println(dirtyPages);
			memoryToTransfer = (long)dirtyPages * source.pageSize;
			dirtyPages = 0;
			migrationBandwidthLimit += (50*globals.Mb/globals.TIME_CONSTANT);
			
			//figure out new amount of memory to transfer
			migrationIteration();
			
		}else{
			if (SimGlobals.DEBUG) {
				System.out.println(printHeader + "No more memory to transfer, restarting");
				double transferTime = (globals.currentTime - startTime)/(double)globals.TIME_CONSTANT;
				System.out.println(printHeader + "Migration took " + transferTime + " seconds.");
				
			}
			
			this.domain.migrating = false;
			this.source.migrating = false;
			this.destination.migrating = false;
			
			if(!sourceComplete)	finalizeSource();
			
			
			domain.memoryPages = domain.memoryAllocation
			/ destination.pageSize;
			
			if (domain.memoryAllocation % destination.pageSize > 0)
				domain.memoryPages += 1;
			
			freeReservedMemory();
			try {
				destination.startDomain(domain);
			} catch (StartError e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			//Clean ourselves up
			source.transferDaemons.remove(this);
			
			
			
		}
		
	}
	
	
	public void finalizeSource(){
		try {

			
			source.stopDomain(domain);
		} catch (StopError e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		sourceComplete = true;

	}
	
	private void reserveMemory(){
		destination.freeMemory -= domain.memoryAllocation;
	}
	
	private void freeReservedMemory(){
		destination.freeMemory += domain.memoryAllocation;
	}

}
